package tech.ula.model.daos

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import android.database.sqlite.SQLiteConstraintException
import androidx.test.InstrumentationRegistry
import androidx.test.filters.SmallTest
import androidx.test.runner.AndroidJUnit4
import org.junit.After
import org.junit.Assert.* // ktlint-disable no-wildcard-imports
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import tech.ula.model.repositories.UlaDatabase
import tech.ula.model.entities.Filesystem
import tech.ula.model.entities.Session
import tech.ula.androidTestHelpers.blockingObserve

@RunWith(AndroidJUnit4::class)
@SmallTest
class SessionDaoTest {

    private lateinit var db: UlaDatabase

    @get:Rule
    val instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setup() {
        db = Room.inMemoryDatabaseBuilder(InstrumentationRegistry.getContext(),
                UlaDatabase::class.java)
                .allowMainThreadQueries()
                .build()
        db.filesystemDao().insertFilesystem(Filesystem(0, name = DEFAULT_FS_NAME))
    }

    @After
    fun closeDb() = db.close()

    // Session Tests
    @Test(expected = SQLiteConstraintException::class)
    fun dbEnforcesUniqueSessionIdConstraint() {
        val session1 = Session(1, name = DEFAULT_NAME, filesystemId = DEFAULT_FS_ID)
        val session2 = Session(1, name = DEFAULT_NAME, filesystemId = DEFAULT_FS_ID)
        db.sessionDao().insertSession(session1)
        db.sessionDao().insertSession(session2)
    }

    @Test
    fun insertSessionAndGetByName() {
        db.sessionDao().insertSession(DEFAULT_SESSION)
        val retrieved = db.sessionDao().getSessionByName(DEFAULT_NAME)
        assertNotNull(retrieved)
        assertEquals(retrieved.name, DEFAULT_NAME)
    }

    @Test
    fun deleteAndFailRetrieval() {
        db.sessionDao().insertSession(DEFAULT_SESSION)
        val id = db.sessionDao().getSessionByName(DEFAULT_NAME).id
        db.sessionDao().deleteSessionById(id)
        assertNull(db.sessionDao().getAllSessions().value)
        assertNull(db.sessionDao().getSessionByName(DEFAULT_NAME))
    }

    @Test
    fun updateSession() {
        val session = Session(DEFAULT_NON_AUTOGENERATED_ID, name = "start", filesystemId = DEFAULT_FS_ID)
        db.sessionDao().insertSession(session)
        assertEquals(session, db.sessionDao().getSessionByName(session.name))

        session.name = "end"
        db.sessionDao().updateSession(session)
        assertEquals(session, db.sessionDao().getSessionByName(session.name))
    }

    @Test
    fun updateFilesystemNamesForAllSessions() {
        val fs = db.filesystemDao().getFilesystemByName(DEFAULT_FS_NAME)

        for (i in 0..9) {
            db.sessionDao().insertSession(Session(DEFAULT_ID, name = "session$i", filesystemId = fs.id))
        }

        fs.name = "end"

        db.filesystemDao().updateFilesystem(fs)
        val sessions = db.sessionDao().getAllSessions().blockingObserve()!!
        for (session in sessions) assertNotEquals(session.filesystemName, fs.name)

        db.sessionDao().updateFilesystemNamesForAllSessions()
        val updatedSessions = db.sessionDao().getAllSessions().blockingObserve()!!
        for (session in updatedSessions) assertEquals(session.filesystemName, fs.name)
    }

    companion object {
        val DEFAULT_ID = 0L
        val DEFAULT_NAME = "test"
        val DEFAULT_FS_ID = 1L
        val DEFAULT_SESSION = Session(DEFAULT_ID, name = DEFAULT_NAME, filesystemId = DEFAULT_FS_ID)

        val DEFAULT_FS_NAME = "start"

        val DEFAULT_NON_AUTOGENERATED_ID = 1L
    }
}